package ymqtt

import (
	"crypto/tls"
	"crypto/x509"
	"dgo/goutils/utils"
	"errors"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/eclipse/paho.mqtt.golang"
)

type MqttService struct {
	url            string
	user           string
	pass           string
	clientid       string
	clinetOptions  *mqtt.ClientOptions
	client         mqtt.Client
	connectTimer   *time.Timer
	onrecvcb       func(topic string, data []byte)
	onconnectcb    func(sender *MqttService)
	ondisconnectcb func(sender *MqttService, err error)
}

func NewMqttService() *MqttService {
	rval := &MqttService{}
	return rval
}

func (this *MqttService) onConnected(client mqtt.Client) {
	if this.onconnectcb != nil {
		this.onconnectcb(this)
	}
}

func (this *MqttService) onDisconnected(client mqtt.Client, err error) {
	if this.ondisconnectcb != nil {
		this.ondisconnectcb(this, err)
	}
}

func (this *MqttService) onRecv(client mqtt.Client, msg mqtt.Message) {
	utils.PanicHandlerWithDebugString(msg.Topic() + "onRecvErr")
	if this.onrecvcb != nil {
		this.onrecvcb(msg.Topic(), msg.Payload())
	}
}

func (this *MqttService) checkTLSConfig() *tls.Config {
	rval := this.clinetOptions.TLSConfig
	if rval == nil {
		rval = &tls.Config{
			// ClientAuth = whether to request cert from server.
			// Since the server is set up for SSL, this happens
			// anyways.
			ClientAuth: tls.NoClientCert,
			// ClientCAs = certs used to validate client cert.
			ClientCAs: nil,
			// InsecureSkipVerify = verify that cert contents
			// match server. IP matches what is in cert etc.
			InsecureSkipVerify: true,
		}
		this.clinetOptions.TLSConfig = rval
	}
	return rval

}

func (this *MqttService) LoadRootCA(filename string) error {
	tlsconf := this.checkTLSConfig()
	certpool := x509.NewCertPool()
	pemCerts, err := ioutil.ReadFile(filename)
	if err == nil {
		certpool.AppendCertsFromPEM(pemCerts)
	} else {
		return err
	}
	tlsconf.RootCAs = certpool
	return nil
}

func (this *MqttService) LoadX509File(certfile, keyfile string) error {
	tlsconf := this.checkTLSConfig()
	cert, err := tls.LoadX509KeyPair(certfile, keyfile)
	if err != nil {
		return err
	}

	cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
	if err != nil {
		return err
	}

	tlsconf.Certificates = []tls.Certificate{cert}
	return nil
}

func (this *MqttService) SetConfig(url, user, pass, clientid string) {
	this.url = url
	this.user = user
	this.pass = pass
	this.clientid = clientid

	cltOptions := mqtt.NewClientOptions()
	cltOptions.AddBroker(url).SetAutoReconnect(true)
	if len(this.user) > 0 {
		cltOptions.SetUsername(user).SetPassword(pass)
	}
	if len(clientid) > 0 {
		cltOptions.SetClientID(clientid)
	}

	cltOptions.SetOnConnectHandler(this.onConnected)
	cltOptions.SetConnectionLostHandler(this.onDisconnected)
	cltOptions.SetConnectTimeout(time.Duration(60) * time.Second)
	this.clinetOptions = cltOptions
}

func (this *MqttService) Start() {
	err := this.Connect()
	if err != nil {
		utils.Log.Errf("连接Mqtt服务器出现异常:%s, 10秒后重试", err.Error())
		this.connectTimer = time.AfterFunc(time.Second*10, this.Start)
	} else {
		this.connectTimer = nil
	}
}

func (this *MqttService) Connect() error {
	this.client = mqtt.NewClient(this.clinetOptions)
	token := this.client.Connect()
	if token.WaitTimeout(time.Second * 5); token.Error() != nil {
		return token.Error()
	}
	if this.client.IsConnected() {
		return nil
	} else {
		this.client = nil
		return errors.New(fmt.Sprintf("connect to %s timeout!", this.url))
	}
}

func (this *MqttService) SetSubscribeCallBack(cb func(topic string, data []byte)) {
	this.onrecvcb = cb
}

func (this *MqttService) SetOnConnectedEvent(cb func(sender *MqttService)) {
	this.onconnectcb = cb
}

func (this *MqttService) SetOnDisconnectedEvent(cb func(sender *MqttService, err error)) {
	this.ondisconnectcb = cb
}

/***
 * 需要先建立连接
 */
func (this *MqttService) Subscribe(topic string) {
	if this.client == nil {
		utils.Log.Warnf("订阅[%s]失败, client尚未赋值", topic)
		return
	}
	recvToken := this.client.Subscribe(topic, 0, this.onRecv)
	recvToken.Wait()
}

func (this *MqttService) IsStarted() bool {
	return this.client != nil
}

func (this *MqttService) Stop() {
	if this.connectTimer != nil {
		this.connectTimer.Stop()
		this.connectTimer = nil
	}
	if this.client != nil {
		this.client.Disconnect(30)
		this.client = nil
	}
}

func (this *MqttService) PublishMsg(msg string, topic string) bool {
	if this.client != nil {
		token := this.client.Publish(topic, 1, false, msg)
		return token.Wait()
	} else {
		return false
	}
}
